O Shiny é um sistema para desenvolvimento de aplicações web usando o R, um pacote do R (shiny) e um servidor web (shiny server).
Shiny é um pacote R que permite criar facilmente aplicativos web interativos e ricos.
Shiny permite que você pegue seu trabalho em R e o exponha por meio de um navegador da Web para que qualquer pessoa possa usá-lo.
Shiny faz você parecer incrível, facilitando a produção de aplicativos da Web com um mínimo de dor.
Shiny torna significativamente mais fácil para o programador de R criar aplicativos da web:
Fornecendo um conjunto cuidadosamente selecionado de funções de interface do usuário ( UI para abreviar) que geram o HTML, CSS e JavaScript necessários para tarefas comuns.
Isso significa que você não precisa conhecer os detalhes de HTML/CSS/JavaScript até que queira ir além do básico que o Shiny fornece para você.
Apresentando um novo estilo de programação chamado programação reativa que rastreia automaticamente as dependências de pedaços de código.
Isso significa que sempre que uma entrada muda, o Shiny pode descobrir automaticamente como fazer a menor quantidade de trabalho para atualizar todas as saídas relacionadas.
As pessoas usam o Shiny para:
Crie painéis que rastreiem importantes indicadores de desempenho de alto nível, enquanto facilitam o detalhamento das métricas que precisam de mais investigação.
Substitua centenas de páginas de PDFs por aplicativos interativos que permitem ao usuário saltar para a fatia exata dos resultados que lhe interessam.
Comunique modelos complexos a um público não técnico com visualizações informativas e análise de sensibilidade interativa.
Crie demonstrações interativas para ensinar estatísticas e conceitos de ciência de dados.
Resumindo, Shiny lhe dá a habilidade de passar alguns de seus superpoderes R para qualquer um que possa usar a web.
Como criar um exemplo que utilize o shiny?
Apresentação ou documento: File > New file > R Markdown
Escolha a opção Shiny, depois documento ou apresentação.
Um ponto positivo é que em um arquivo definido assim podemos utilizar os gráficos interativos dos pacotes ggvis, ggiraph, plotly, etc.
Dois principais componentes de cada aplicativo Shiny:
UI (abreviação de interface do usuário) que define a aparência do seu aplicativo;
função do servidor que define como seu aplicativo funciona.
Shiny usa programação reativa para atualizar automaticamente as saídas quando as entradas mudam, então vamos aprender sobre expressões reativas.
Carregue em sua sessão R atual:
library(shiny)
Criar diretório e arquivo do aplicativo
Existem várias maneiras de criar um aplicativo Shiny.
O mais simples é criar um novo diretório para seu aplicativo e colocar um único arquivo chamado app.R nele.
Se você já criou o arquivo app.R, pode adicionar rapidamente o código comum do aplicativo digitando “shinyapp” e pressionando Shift+Tab.
Crie um novo diretório e um arquivo app.R contendo um aplicativo básico em uma etapa clicando em Arquivo | Novo Projeto e, em seguida, selecionando Novo Diretório e Aplicativo Web Shiny .
Experimente criando um novo diretório e adicionando um arquivo app.R parecido com este:
Olhando atentamente para o código acima, o nosso app.R faz quatro coisas:
Ele chama library(shiny) para carregar o pacote shiny.
Ele define a interface do usuário, a página da Web HTML com a qual os humanos interagem.
Neste caso, é uma página que contém as palavras “Hello, world!”.
Ele especifica o comportamento do nosso aplicativo definindo uma função server.
No momento, está vazio, então nosso aplicativo não faz nada.
Ele é executado shinyApp(ui, server) para construir e iniciar um aplicativo Shiny a partir da interface do usuário e do servidor.
Run e Stop
Existem algumas maneiras de executar este aplicativo:
Clique no botão Executar aplicativo na barra de ferramentas do documento.
Use um atalho de teclado: Cmd/Ctrl+ Shift+ Enter.
Se você não estiver usando o RStudio, você pode (source()) em todo o documento, ou chamar shiny::runApp() com o caminho para o diretório que contém app.R.
Antes de fechar o aplicativo, volte ao RStudio e veja o console R.
Você notará que ele diz algo como:
#> Listening on http://127.0.0.1:3827
Isso informa a URL onde seu aplicativo pode ser encontrado: 127.0.0.1 é um endereço padrão que significa “este computador” e 3827 é um número de porta atribuído aleatoriamente.
Você pode inserir esse URL em qualquer navegador compatível para abrir outra cópia do aplicativo.
Observe também que R está ocupado.
Você pode parar o aplicativo e retornar o acesso ao console usando qualquer uma destas opções:
Clique no ícone do sinal de parada na barra de ferramentas do console R.
O lado esquerdo do operador de atribuição, output$ID, indica que você está fornecendo a receita para a saída Shiny com esse ID.
O lado direito da atribuição usa uma função de renderização específica para agrupar algum código que você fornece.
Cada função render{Type} é projetada para produzir um tipo específico de saída e é associada a uma função {type}Output.
Observe que o resumo e a tabela são atualizados sempre que você altera o conjunto de dados de entrada.
Essa dependência é criada implicitamente porque nos referimos input$dataset nas funções de saída.
input$dataset é preenchido com o valor atual do componente de interface do usuário com id dataset e fará com que as saídas sejam atualizadas automaticamente sempre que esse valor for alterado.
Reduzindo a duplicação com expressões reativas
Temos algum código duplicado: a seguinte linha está presente em ambas as saídas.
dataset <- get(input$dataset, "package:datasets")
Em todo tipo de programação, é uma prática ruim ter código duplicado;
pode ser um desperdício computacional e, mais importante, aumenta a dificuldade de manter ou depurar o código.
Precisamos de um novo mecanismo: expressões reativas.
Você cria uma expressão reativa envolvendo um bloco de código reactive({...}) e atribuindo-o a uma variável
Usa uma expressão reativa chamando-a como uma função.
server <- function(input, output, session) {
# Create a reactive expression
dataset <- reactive({
get(input$dataset, "package:datasets")
})
output$summary <- renderPrint({
# Use a reactive expression by calling it like a function
summary(dataset())
})
output$table <- renderTable({
dataset()
})
}
IU básica
Introdução
Shiny incentiva a separação do código que gera sua interface de usuário (o front-end) do código que orienta o comportamento do seu aplicativo (o back-end).
Existem entradas e saídas incorporadas ao próprio Shiny.
Todas as funções de entrada têm o mesmo primeiro argumento: inputId.
Este é o identificador usado para conectar o front-end com o back-end: se sua UI tiver uma entrada com ID "name", a função do servidor irá acessá-la com input$name.
O inputId tem duas restrições:
Deve ser uma string simples que contenha apenas letras, números e sublinhados.
Deve ser único.
A maioria das funções de entrada tem um segundo parâmetro chamado label.
Isso é usado para criar um rótulo legível para o controle.
O terceiro parâmetro é normalmente value, que, sempre que possível, permite definir o valor padrão.
Os demais parâmetros são exclusivos de cada controle.
ui <- fluidPage(
textInput("name", "What's your name?"),
passwordInput("password", "What's your password?"),
textAreaInput("story", "Tell me about yourself", rows = 3)
)
Entradas numéricas
Para coletar valores numéricos, crie uma caixa de texto restrita com numericInput() ou um controle deslizante com sliderInput().
ui <- fluidPage(
numericInput("num", "Number one", value = 0, min = 0, max = 100),
sliderInput("num2", "Number two", value = 50, min = 0, max = 100),
sliderInput("rng", "Range", value = c(10, 20), min = 0, max = 100) )
Datas
Colete um único dia com dateInput() ou um intervalo de dois dias com dateRangeInput().
argumentos adicionais como datesdisabled e daysofweekdisabled permitem que você restrinja o conjunto de entradas válidas.
ui <- fluidPage(
dateInput("dob", "When were you born?"),
dateRangeInput("holiday", "When do you want to go on vacation next?")
)
Escolhas limitadas
Existem duas abordagens diferentes para permitir que o usuário escolha entre um conjunto pré-especificado de opções: selectInput() e radioButtons().
animals <- c("dog", "cat", "mouse", "bird", "other", "I hate animals")
ui <- fluidPage(
selectInput("state", "What's your favourite state?", state.name),
radioButtons("animal", "What's your favourite animal?", animals)
)
os botões de opções (radioButtons) mostra todas as opções possíveis, tornando-os adequados para listas curtas e, por meio dos argumentos choiceNames/ choiceValues.
choiceNames determina o que é mostrado ao usuário; choiceValues determina o que é retornado em sua função de servidor.
As listas suspensas criadas com selectInput() ocupam a mesma quantidade de espaço, independentemente do número de opções, tornando-as mais adequadas para opções mais longas.
Você também pode definir multiple = TRUE para permitir que o usuário selecione vários elementos.
Links e botões de ações são mais naturalmente emparelhados com observeEvent() ou eventReactive() em sua função de servidor.
Você pode personalizar a aparência usando o argumento class:
ui <- fluidPage(
fluidRow(
actionButton("click", "Click me!", class = "btn-danger"),
actionButton("drink", "Drink me!", class = "btn-lg btn-success") ),
fluidRow( actionButton("eat", "Eat me!", class = "btn-block") )
)
Saídas
As saídas na interface do usuário criam espaços reservados que são posteriormente preenchidos pela função do servidor.
As saídas recebem um ID exclusivo como primeiro argumento: se sua especificação de interface do usuário criar uma saída com ID "plot", você a acessará na função do servidor com output$plot.
Cada função output no front-end é acoplada a uma função render no back-end.
Existem duas opções para exibir quadros de dados em tabelas:
tableOutput() e renderTable() renderizam uma tabela estática de dados, mostrando todos os dados de uma vez.
dataTableOutput() e renderDataTable() renderizam uma tabela dinâmica, mostrando um número fixo de linhas junto com controles para alterar quais linhas são visíveis.
tableOutput() é mais útil para resumos pequenos e fixos (por exemplo, coeficientes de modelo); dataTableOutput() é mais apropriado se você deseja expor um quadro de dados completo ao usuário.
A programação reativa é um paradigma de programação elegante e poderoso, mas pode ser desorientador no início porque é um paradigma muito diferente de escrever um script.
A ideia-chave da programação reativa é especificar um grafo de dependências para que, quando uma entrada for alterada, todas as saídas relacionadas sejam atualizadas automaticamente.
A função do servidor
As entranhas de cada aplicativo Shiny são assim:
library(shiny)
ui <- fluidPage(
# front end interface
)
server <- function(input, output, session) {
# back end logic
}
shinyApp(ui, server)
O objeto ui que contém o HTML apresentado a todos os usuários do seu aplicativo.
O ui é simples porque cada usuário recebe o mesmo HTML.
server é mais complicado porque cada usuário precisa obter uma versão independente do aplicativo.
Para obter essa independência, o Shiny invoca sua função server() toda vez que uma nova sessão é iniciada.
Quando a função do servidor é chamada, ela cria um novo ambiente local que é independente de qualquer outra invocação da função.
Isso permite que cada sessão tenha um estado único, além de isolar as variáveis criadas dentro da função.
As funções do servidor usam três parâmetros: input, output e session.
eles são criados pelo Shiny quando a sessão começa, conectando-se novamente a uma sessão específica.
Entrada
O argumento input é um objeto semelhante a uma lista que contém todos os dados de entrada enviados do navegador, nomeados de acordo com o ID de entrada.
Por exemplo, se sua IU contiver um controle de entrada numérico com um ID de entrada de count, assim:
ui <- fluidPage(
numericInput("count", label = "Number of values", value = 100)
)
Você pode acessar o valor dessa entrada com input$count.
Ele conterá inicialmente o valor 100 e será atualizado automaticamente conforme o usuário altera o valor no navegador.
Ao contrário de uma lista típica, os objetos input são somente leitura.
Se você tentar modificar uma entrada dentro da função do servidor, receberá um erro:
server <- function(input, output, session) {
input$count <- 10
}
shinyApp(ui, server)
#> Error: Can't modify read-only reactive value 'count'
Este erro ocorre porque input reflete o que está acontecendo no navegador, e o navegador é a “única fonte de verdade” do Shiny.
Mais uma coisa importante input: é seletivo sobre quem tem permissão para lê-lo.
Para ler de um input, você deve estar em um contexto reativo criado por uma função como renderText() ou reactive().
Este código ilustra o erro que você verá se cometer este erro:
server <- function(input, output, session) {
message("The value of input$count is ", input$count)
}
shinyApp(ui, server)
#> Error: Can't access reactive value 'count' outside of reactive consumer.
#> ℹ Do you need to wrap inside reactive() or observer()?
Saída
output é muito semelhante a input: também é um objeto semelhante a uma lista nomeado de acordo com o ID de saída.
A principal diferença é que você o usa para enviar saída em vez de receber entrada.
Você sempre usa o objeto output em conjunto com uma função render, como no exemplo simples a seguir:
Ele configura um contexto reativo especial que rastreia automaticamente quais entradas a saída usa.
Ele converte a saída do seu código R em HTML adequado para exibição em uma página da web.
Como o input, o output é exigente sobre como você o usa. Você receberá um erro se:
Você esquece a função render.
server <- function(input, output, session) {
output$greeting <- "Hello human"
}
shinyApp(ui, server)
#> Error: Unexpected character object for output$greeting
#> ℹ Did you forget to use a render function?
Você tenta ler de uma saída.
server <- function(input, output, session) {
message("The greeting is ", output$greeting)
}
shinyApp(ui, server)
#> Error: Reading from shinyoutput object is not allowed.
É fácil ler isso como “cole ‘hello’ e o nome do usuário e envie para output$greeting”.
Mas esse modelo mental está errado de uma maneira sutil, mas importante.
O aplicativo funciona porque o código não diz ao Shiny para criar a string e enviá-la ao navegador, mas, em vez disso, informa ao Shiny como ele pode criar a string se for necessário.
Cabe a Shiny quando (e mesmo se!) o código deve ser executado.
Pense em seu aplicativo como fornecendo receitas ao Shiny, não dando comandos.
Programação imperativa x declarativa
Na programação imperativa , você emite um comando específico e ele é executado imediatamente.
Na programação declarativa, você expressa metas de alto nível ou descreve restrições importantes e confia em outra pessoa para decidir como e/ou quando traduzir isso em ação.
Este é o estilo de programação que você usa no Shiny.
Preguiça
Um dos pontos fortes da programação declarativa no Shiny é que ela permite que os aplicativos sejam extremamente preguiçosos.
Um aplicativo Shiny só fará a quantidade mínima de trabalho necessária para atualizar os controles de saída que você pode ver atualmente.
Essa preguiça, no entanto, vem com uma desvantagem importante que você deve estar ciente.
Você consegue identificar o que há de errado com a função do servidor abaixo?
Esse código produz o mesmo gráfico reativo acima, portanto, a ordem na qual o código é executado é exatamente a mesma.
Organizar seu código assim é confuso para humanos e é melhor evitar.
Expressões reativas
As expressões reativas são importantes porque:
fornecem ao Shiny mais informações para que ele possa fazer menos recomputação quando as entradas mudam, tornando os aplicativos mais eficientes
facilitam a compreensão do aplicativo por humanos simplificando o gráfico reativo.
As expressões reativas têm um sabor de entradas e saídas:
Assim como as entradas, você pode usar os resultados de uma expressão reativa em uma saída.
Assim como as saídas, as expressões reativas dependem das entradas e sabem automaticamente quando precisam ser atualizadas.
A motivação
Imagine que eu queira comparar dois conjuntos de dados simulados com um gráfico e um teste de hipótese.
Por exemplo: freqpoly() visualiza as duas distribuições com polígonos de frequência , e t_test() usa um teste t para comparar médias e resume os resultados com uma string:
Se eu tiver alguns dados simulados, posso usar essas funções para comparar duas variáveis:
x1 <-rnorm(100, mean =0, sd =0.5) x2 <-rnorm(200, mean =0.15, sd =0.9)freqpoly(x1, x2)
cat(t_test(x1, x2))
p value: 0.056
[-0.31, 0.00]
Extrair código imperativo em funções regulares é uma técnica importante para todos os aplicativos Shiny: quanto mais código você puder extrair do seu aplicativo, mais fácil será entender.
O aplicativo
Gostaria de usar essas ferramentas para explorar rapidamente um monte de simulações.
Um aplicativo Shiny é uma ótima maneira de fazer isso porque permite que você evite modificar e executar novamente o código R de forma tediosa.
Vamos começar com a interface do usuário.
A primeira linha tem três colunas para controles de entrada (distribuição 1, distribuição 2 e controles de plotagem).
A segunda linha tem uma coluna larga para o gráfico e uma coluna estreita para o teste de hipótese.
ui <- fluidPage(
fluidRow(
column(4,
"Distribution 1",
numericInput("n1", label = "n", value = 1000, min = 1),
numericInput("mean1", label = "µ", value = 0, step = 0.1),
numericInput("sd1", label = "σ", value = 0.5, min = 0.1, step = 0.1)
),
column(4,
"Distribution 2",
numericInput("n2", label = "n", value = 1000, min = 1),
numericInput("mean2", label = "µ", value = 0, step = 0.1),
numericInput("sd2", label = "σ", value = 0.5, min = 0.1, step = 0.1)
),
column(4,
"Frequency polygon",
numericInput("binwidth", label = "Bin width", value = 0.1, step = 0.1),
sliderInput("range", label = "range", value = c(-3, 3), min = -5, max = 5)
)
),
fluidRow(
column(9, plotOutput("hist")),
column(3, verbatimTextOutput("ttest"))
)
)
A função do servidor combina chamadas para funções freqpoly() e t_test() após extrair das distribuições especificadas:
Como um humano lendo este código, você pode dizer que só precisamos atualizar x1 quando n1, mean1, ou sd1 muda, e só precisamos atualizar x2 quando n2, mean2, ou sd2 muda.
Shiny, no entanto, apenas analisa a saída como um todo, portanto, atualizará ambos x1 e x2 sempre que um de n1, mean1, sd1, n2, mean2 ou for sd2 alterado.
Você notará que o gráfico é muito denso: quase todas as entradas estão conectadas diretamente a todas as saídas. Isso cria dois problemas:
O aplicativo é ineficiente porque faz mais trabalho do que o necessário.
Por exemplo, se você alterar as quebras do gráfico, os dados serão recalculados; se você alterar o valor de n1, x2 é atualizado (em dois lugares!).
Há uma outra grande falha no aplicativo: o polígono de frequência e o teste t usam sorteios aleatórios separados.
Isso é bastante enganoso, pois você esperaria que eles estivessem trabalhando nos mesmos dados.
Simplificando o gráfico
Refatoramos o código existente para extrair o código repetido em duas novas expressões reativas, x1 e x2, que simulam os dados das duas distribuições.
Para criar uma expressão reativa, chamamos reactive() e atribuímos os resultados a uma variável.
Para usar a expressão posteriormente, chamamos a variável como se fosse uma função.
Essa transformação produz o gráfico substancialmente mais simples.
Agora, quando você altera binwidth ou range, apenas o gráfico muda, não os dados subjacentes.
No Shiny, acho que você deve considerar a regra de um: sempre que você copiar e colar algo uma vez, considere extrair o código repetido em uma expressão reativa.
A regra é mais rígida para o Shiny porque as expressões reativas não apenas tornam mais fácil para os humanos entenderem o código, mas também melhoram a capacidade do Shiny de executar novamente o código com eficiência.
Por que precisamos de expressões reativas?
Se você tentar usar uma variável para reduzir a duplicação, poderá escrever algo assim:
Mas tem o mesmo problema que o código original: qualquer entrada fará com que todas as saídas sejam recalculadas, e o teste t e o polígono de frequência serão executados em amostras separadas.
Expressões reativas armazenam automaticamente seus resultados em cache e são atualizadas apenas quando suas entradas mudam.
Controlando o tempo de avaliação
Para explorar as ideias básicas, vou simplificar o aplicativo de simulação.
Isso não atinge nosso objetivo porque apenas introduz uma nova dependência: x1() e x2() será atualizado quando clicarmos no botão simular, mas eles também continuarão a atualizar quando alterar lambda1, lambda2, ou n.
Queremos substituir as dependências existentes, não adicioná-las.
Para resolver este problema, precisamos de uma nova ferramenta: uma maneira de usar valores de entrada sem ter uma dependência reativa deles.
Precisamos de eventReactive(), que tem dois argumentos: o primeiro argumento especifica sobre o que tomar uma dependência e o segundo argumento especifica o que computar.
Mesmo que você ainda não tenha aprendido essas funções, você pode adivinhar o que está acontecendo lendo seus nomes.
Funções da página
A função de layout mais importante, mas menos interessante, é fluidPage()
fluidPage() configura todo o HTML, CSS e JavaScript que o Shiny precisa.
Além de fluidPage(), Shiny fornece algumas outras funções de página que podem ser úteis em situações mais especializadas: fixedPage() e fillPage().
fixedPage() funciona como fluidPage(), mas tem uma largura máxima fixa, o que impede que seus aplicativos se tornem excessivamente amplos em telas maiores.
fillPage() preenche toda a altura do navegador e é útil se você quiser fazer um gráfico que ocupe toda a tela. Você pode encontrar os detalhes em sua documentação.
Página com barra lateral
Para criar layouts mais complexos, você precisará chamar funções de layout dentro de fluidPage().
Cada linha é composta por 12 colunas e o primeiro argumento para column() fornece quantas dessas colunas devem ser ocupadas.
Um layout de 12 colunas oferece flexibilidade substancial porque você pode criar facilmente layouts de 2, 3 ou 4 colunas ou usar colunas estreitas para criar espaçadores.
Layouts de várias páginas
À medida que seu aplicativo cresce em complexidade, pode se tornar impossível ajustar tudo em uma única página.
Tabsets
A maneira simples de quebrar uma página em pedaços é usar tabsetPanel() e seu amigo próximo tabPanel().
tabsetPanel() cria um container para qualquer número de tabPanels(), que por sua vez pode conter quaisquer outros componentes HTML.
ui <- fluidPage(
tabsetPanel(
tabPanel("Import data",
fileInput("file", "Data", buttonLabel = "Upload..."),
textInput("delim", "Delimiter (leave blank to guess)", ""),
numericInput("skip", "Rows to skip", 0, min = 0),
numericInput("rows", "Rows to preview", 10, min = 1) ),
tabPanel("Set parameters"), tabPanel("Visualise results")
)
)
Se você quiser saber qual guia um usuário selecionou, você pode fornecer o argumento id de tabsetPanel() e ele se torna uma entrada.
Como as guias são exibidas horizontalmente, há um limite fundamental para quantas guias você pode usar, principalmente se tiverem títulos longos.
navbarPage() e navbarMenu() fornecem dois layouts alternativos que permitem usar mais guias com títulos mais longos.
navlistPanel() é semelhante, tabsetPanel() mas em vez de executar os títulos das guias horizontalmente, ele os mostra verticalmente em uma barra lateral.
ui <- fluidPage(
navlistPanel( id = "tabset", "Heading 1",
tabPanel("panel 1", "Panel one contents"), "Heading 2",
tabPanel("panel 2", "Panel two contents"),
tabPanel("panel 3", "Panel three contents")
)
)
Outra abordagem é usar navbarPage(): ele ainda executa os títulos das guias horizontalmente, mas você pode usar navbarMenu() para adicionar menus suspensos para um nível adicional de hierarquia.
Introdução Shiny incentiva a separação do código que gera sua interface de usuário (o front-end) do código que orienta o comportamento do seu aplicativo (o back-end) . Existem entradas e saídas incorporadas ao próprio Shiny. Existe uma comunidade rica e vibrante de pacotes de extensão, como shinyWidgets , colorpicker e sorttable . Você usa funções como sliderInput() , selectInput() , textInput() e numericInput() para inserir controles de entrada em sua especificação de UI.